/*
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 
 * MA  02110-1301, USA.
 *
 * http://www.gnu.org/copyleft/gpl.html
 */
package com.aionemu.loginserver.network.aion;

import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SocketChannel;
import java.security.interfaces.RSAPrivateKey;

import javax.crypto.SecretKey;

import org.apache.log4j.Logger;

import com.aionemu.commons.network.mmocore.MMOConnection;
import com.aionemu.commons.network.mmocore.SelectorThread;
import com.aionemu.loginserver.controller.AccountController;
import com.aionemu.loginserver.controller.AccountTimeController;
import com.aionemu.loginserver.model.Account;
import com.aionemu.loginserver.network.aion.serverpackets.SM_INIT;
import com.aionemu.loginserver.network.ncrypt.CryptEngine;
import com.aionemu.loginserver.network.ncrypt.EncryptedRSAKeyPair;
import com.aionemu.loginserver.network.ncrypt.KeyGen;
import com.aionemu.loginserver.utils.FloodProtector;

/**
 * @author Mr. Poke
 *
 */
public class GameClient extends MMOConnection<GameClient>
{
	private static final Logger			log			= Logger.getLogger(GameClient.class);

	private Account						account;

	/**
	 * Unique Session Id of this connection
	 */
	private final int					sessionId	= hashCode();

	/**
	 * Crypt to encrypt/decrypt packets
	 */
	private final CryptEngine			cryptEngine;

	/**
	 * Scrambled key pair for RSA
	 */
	private final EncryptedRSAKeyPair	encryptedRSAKeyPair;

	/**
	 * Session Key for this connection.
	 */
	private SessionKey					sessionKey;

	/**
	 * True if this user is connecting to GS.
	 */
	private boolean						joinedGs;

	private volatile boolean			disconnected;

	/**
	 * @param selectorThread
	 * @param socketChannel
	 * @throws ClosedChannelException
	 */
	protected GameClient(SelectorThread<GameClient> selectorThread, SocketChannel socketChannel) throws ClosedChannelException
	{
		super(selectorThread, socketChannel);
		if (FloodProtector.getInstance().addIp(this.getInetAddress().getHostAddress()))
			closeNow();
		encryptedRSAKeyPair = KeyGen.getEncryptedRSAKeyPair();
		SecretKey blowfishKey = KeyGen.generateBlowfishKey();
		cryptEngine = new CryptEngine();
		cryptEngine.updateKey(blowfishKey.getEncoded());
		log.info("Client connected Ip:" + this.getInetAddress().getHostName());
		sendPacket(new SM_INIT(this, blowfishKey));
	}

	public int getSessionId()
	{
		return sessionId;
	}

	public CryptEngine getCryptEngine()
	{
		return cryptEngine;
	}

	public Account getAccount()
	{
		return account;
	}

	/**
	 * @param account The account to set.
	 */
	public void setAccount(Account account)
	{
		this.account = account;
	}

	/**
	 * Return RSA private key
	 * 
	 * @return rsa private key
	 */
	public final RSAPrivateKey getRSAPrivateKey()
	{
		return (RSAPrivateKey) encryptedRSAKeyPair.getRSAKeyPair().getPrivate();
	}

	public SessionKey getSessionKey()
	{
		return sessionKey;
	}

	/**
	 * Set Session Key for this connection
	 * 
	 * @param sessionKey
	 */
	public final void setSessionKey(SessionKey sessionKey)
	{
		this.sessionKey = sessionKey;
	}

	/**
	 * Set joinedGs value to true
	 */
	public final void setJoinedGs()
	{
		joinedGs = true;
	}

	/**
	 * Return Scrambled modulus
	 * 
	 * @return Scrambled modulus
	 */
	public final byte[] getEncryptedModulus()
	{
		return encryptedRSAKeyPair.getEncryptedModulus();
	}

	@Override
	protected void onDisconnection()
	{
		setDisconnected();
	}

	void setDisconnected()
	{
		if (account != null)
		{
			if (joinedGs)
				log.info("Client logged in IP: " + this.getInetAddress().getHostName().toString() + ", AccountName: " + account.getName());
			else
			{
				AccountController.getInstance().removeAccountOnLS(account);
				AccountTimeController.updateOnLogout(account);
				log.info("Client can't logged in IP: " + this.getInetAddress().getHostName().toString());
			}
		}
		else
			log.info("Client can't logged in IP: " + this.getInetAddress().getHostName().toString());
		disconnected = true;
	}

	boolean isDisconnected()
	{
		return disconnected;
	}

	@Override
	protected void onForcedDisconnection()
	{
		log.debug("Client " + toString() + " disconnected abnormally.");
	}

	@Override
	protected boolean decrypt(ByteBuffer buf, int size)
	{
		return cryptEngine.decrypt(buf.array(), 2, size);
	}

	@Override
	protected void encrypt(ByteBuffer buf, int size)
	{
		int lenght = cryptEngine.encrypt(buf.array(), 2, size);
		buf.position(lenght+2);
	}

	@Override
	protected String getUID()
	{
		return account == null ? "" : account.getName();
	}
}
